package mips.instructions;

import java.util.HashMap;
import java.util.Map;

import mips.structure.MipsInstr;
import mips.value.Immediate;
import mips.value.MipsValue;
import optimizer.ArithmeticOptimizer;

public class MipsBinaryOp extends MipsInstr {
    public MipsValue dst;
    public MipsValue op1;
    public MipsValue op2;
    public InstrType type;

    public MipsBinaryOp(String type, MipsValue dst, MipsValue op1, MipsValue op2) {
        this.dst = dst;
        this.op1 = op1;
        this.op2 = op2;
        this.type = map.get(type);
    }

    public enum InstrType {
        addu,
        subu,
        mul,
        div,
        rem,
        sll,
        srl,
        sle,
        sgt,
        sne,
        seq,
        slt,
        sge,
        and
    }

    private Map<String, InstrType> map = new HashMap<>();

    {
        map.put("+", InstrType.addu);
        map.put("-", InstrType.subu);
        map.put("*", InstrType.mul);
        map.put("/", InstrType.div);
        map.put("%", InstrType.rem);
        map.put("<<", InstrType.sll);
        map.put(">>", InstrType.srl);
        map.put("<=", InstrType.sle);
        map.put(">", InstrType.sgt);
        map.put("!=", InstrType.sne);
        map.put("==", InstrType.seq);
        map.put("<", InstrType.slt);
        map.put(">=", InstrType.sge);
        map.put("&", InstrType.and);
    }

    public String toAddString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() + ((Immediate) op2).getValue();
            return "li " + dst + ", " + value;
        } else if (op1 instanceof Immediate) {
            return "addiu " + dst + ", " + op2 + ", " + op1;
        } else if (op2 instanceof Immediate) {
            return "addiu " + dst + ", " + op1 + ", " + op2;
        } else {
            return "addu " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toSubString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() - ((Immediate) op2).getValue();
            return "li " + dst + ", " + value;
        } else if (op1 instanceof Immediate) {
            // 先给寄存器取反，再加上立即数
            if (((Immediate) op1).getValue() == 0)
                return "subu " + dst + ", $zero, " + op2;
            return "subu " + op2 + ", $zero, " + op2 + "\naddiu " + dst + ", " + op2 + ", "
                    + ((Immediate) op1).getValue();
        } else if (op2 instanceof Immediate) {
            return "addiu " + dst + ", " + op1 + ", " + (-((Immediate) op2).getValue());
        } else {
            return "subu " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toSllString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() << ((Immediate) op2).getValue();
            return "li " + dst + ", " + value;
        } else {
            return "sll " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toSrlString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() >> ((Immediate) op2).getValue();
            return "li " + dst + ", " + value;
        } else {
            return "srl " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toSgtString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() > ((Immediate) op2).getValue() ? 1 : 0;
            return "li " + dst + ", " + value;
        } else if (op2 instanceof Immediate) {
            return "sgt " + dst + ", " + op1 + ", " + op2;
        } else if (op1 instanceof Immediate) {
            return "sle " + dst + ", " + op2 + ", " + op1;
        } else {
            return "sgt " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toSltString() {
        if (op2 instanceof Immediate &&
                (((Immediate) op2).getValue() >= 0x7FFF || ((Immediate) op2).getValue() < -0x8000)) {
            // 如果立即数大于等于0x7FFF或者小于-0x8000，不能用slti
            return "li $v1, " + op2 + "\nslt " + dst + ", " + op1 + ", $v1";
        } else if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() < ((Immediate) op2).getValue() ? 1 : 0;
            return "li " + dst + ", " + value;
        } else if (op2 instanceof Immediate) {
            return "slti " + dst + ", " + op1 + ", " + op2;
        } else if (op1 instanceof Immediate) {
            return "sge " + dst + ", " + op2 + ", " + op1;
        } else {
            return "slt " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toSleString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() <= ((Immediate) op2).getValue() ? 1 : 0;
            return "li " + dst + ", " + value;
        } else if (op2 instanceof Immediate) {
            return "sle " + dst + ", " + op1 + ", " + op2;
        } else if (op1 instanceof Immediate) {
            return "sgt " + dst + ", " + op2 + ", " + op1;
        } else {
            return "sle " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toSneString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() != ((Immediate) op2).getValue() ? 1 : 0;
            return "li " + dst + ", " + value;
        } else if (op1 instanceof Immediate) {
            return "sne " + dst + ", " + op2 + ", " + op1;
        } else {
            return "sne " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toSeqString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() == ((Immediate) op2).getValue() ? 1 : 0;
            return "li " + dst + ", " + value;
        } else if (op1 instanceof Immediate) {
            return "seq " + dst + ", " + op2 + ", " + op1;
        } else {
            return "seq " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toSgeString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() >= ((Immediate) op2).getValue() ? 1 : 0;
            return "li " + dst + ", " + value;
        } else if (op2 instanceof Immediate) {
            return "sge " + dst + ", " + op1 + ", " + op2;
        } else if (op1 instanceof Immediate) {
            if (((Immediate) op1).getValue() >= 0x7FFF || ((Immediate) op1).getValue() < -0x8000) {
                return "li $v1, " + op1 + "\nslt " + dst + ", $v1, " + op2;
            }
            return "slti " + dst + ", " + op2 + ", " + op1;
        } else {
            return "sge " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toMulString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() * ((Immediate) op2).getValue();
            return "li " + dst + ", " + value;
        } else if (op1 instanceof Immediate) {
            return "mulu " + dst + ", " + op2 + ", " + op1;
        } else if (op2 instanceof Immediate) {
            return "mulu " + dst + ", " + op1 + ", " + op2;
        } else {
            return "mulu " + dst + ", " + op1 + ", " + op2;
        }
    }

    public String toDivString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            // 向0取整
            int value = ((Immediate) op1).getValue() / ((Immediate) op2).getValue();
            return "li " + dst + ", " + value;
        } else if (op1 instanceof Immediate) {
            return "li $v0, " + op1 + "\ndiv $v0, " + op2 + "\nmflo " + dst;
        } else if (op2 instanceof Immediate) {
            if (ArithmeticOptimizer.OPEN) {
                return ArithmeticOptimizer.optimizeDivision(op1, op2, dst, "div");
            } else
                return "li $v0, " + op2 + "\ndiv " + op1 + ", $v0\nmflo " + dst;
        } else {
            return "div " + op1 + ", " + op2 + "\nmflo " + dst;
        }
    }

    public String toRemString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() % ((Immediate) op2).getValue();
            return "li " + dst + ", " + value;
        } else if (op1 instanceof Immediate) {
            return "li $v0, " + op1 + "\ndiv $v0, " + op2 + "\nmfhi " + dst;
        } else if (op2 instanceof Immediate) {
            if (ArithmeticOptimizer.OPEN) {
                return ArithmeticOptimizer.optimizeDivision(op1, op2, dst, "rem");
            } else
                return "li $v0, " + op2 + "\ndiv " + op1 + ", $v0\nmfhi " + dst;
        } else {
            return "div " + op1 + ", " + op2 + "\nmfhi " + dst;
        }
    }

    public String toAndString() {
        if (op1 instanceof Immediate && op2 instanceof Immediate) {
            int value = ((Immediate) op1).getValue() & ((Immediate) op2).getValue();
            return "li " + dst + ", " + value;
        } else if (op1 instanceof Immediate) {
            return "andi " + dst + ", " + op2 + ", " + op1;
        } else if (op2 instanceof Immediate) {
            return "andi " + dst + ", " + op1 + ", " + op2;
        } else {
            return "and " + dst + ", " + op1 + ", " + op2;
        }
    }

    @Override
    public String toString() {
        if (type == InstrType.addu) {
            return toAddString();
        } else if (type == InstrType.subu) {
            return toSubString();
        } else if (type == InstrType.mul) {
            return toMulString();
        } else if (type == InstrType.div) {
            return toDivString();
        } else if (type == InstrType.rem) {
            return toRemString();
        } else if (type == InstrType.sll) {
            return toSllString();
        } else if (type == InstrType.srl) {
            return toSrlString();
        } else if (type == InstrType.sle) {
            return toSleString();
        } else if (type == InstrType.sgt) {
            return toSgtString();
        } else if (type == InstrType.sne) {
            return toSneString();
        } else if (type == InstrType.seq) {
            return toSeqString();
        } else if (type == InstrType.slt) {
            return toSltString();
        } else if (type == InstrType.sge) {
            return toSgeString();
        } else if (type == InstrType.and) {
            return toAndString();
        } else {
            return null;
        }
    }

}
